/*
 *  Copyright 2001-2004 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ar.uba.fi.posgrado.economy.service.impl;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
import org.apache.log4j.Logger;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import ar.uba.fi.posgrado.academy.model.Body;
import ar.uba.fi.posgrado.academy.model.Teacher;
import ar.uba.fi.posgrado.common.dao.DAOException;
import ar.uba.fi.posgrado.common.exception.MissingArgumentException;
import ar.uba.fi.posgrado.common.exception.PosgradoException;
import ar.uba.fi.posgrado.common.exception.PosgradoRuntimeException;
import ar.uba.fi.posgrado.common.model.User;
import ar.uba.fi.posgrado.common.util.CommonUtils;
import ar.uba.fi.posgrado.common.util.CriterionBuilder;
import ar.uba.fi.posgrado.common.util.ExpressionOperator;
import ar.uba.fi.posgrado.economy.dao.HonoraryDAO;
import ar.uba.fi.posgrado.economy.model.CustomerAccount;
import ar.uba.fi.posgrado.economy.model.DebtStatus;
import ar.uba.fi.posgrado.economy.model.Honorary;
import ar.uba.fi.posgrado.economy.model.ProviderAccount;
import ar.uba.fi.posgrado.economy.service.HonoraryService;

/**
 * Implementacion de servicio de {@link Honorary}.<br>
 * 
 * @author nicolas.gonzalez
 * @see HonoraryService
 */
public class HonoraryServiceImpl implements HonoraryService {

    /** Logger */
    private static Logger logger = Logger.getLogger(HonoraryServiceImpl.class);
    
    private HonoraryDAO honoraryDAO;
    
    /**
     * @param honoraryDAO the honoraryDAO to set
     */
    public void setHonoraryDAO(HonoraryDAO honoraryDAO) {
        this.honoraryDAO = honoraryDAO;
    }

    /** 
	 * @see ar.uba.fi.posgrado.economy.service.HonoraryService#executeOnlineJobs(ar.uba.fi.posgrado.common.model.User)
	 */
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void executeOnlineJobs(User user) throws PosgradoException {
		logger.info("Por ejecutar el servicio de activacion de honorarios en forma online ...");

		if (!user.hasAdminRole()) {
			throw new PosgradoException("El usuario: " + user + " no es administrador. " +
										"No puede ejecutar los servicios online");
		}

		this.activateHonoraries();
		
		logger.info("Servicio de activacion de honorarios en forma online ejecutado exitosamente...");
	}



	/**
     * @see ar.uba.fi.posgrado.economy.service.HonoraryService#getHonorariesByConditions(ar.uba.fi.posgrado.economy.model.Honorary)
     */
    @Transactional(readOnly = true)
    public List<Honorary> getHonorariesByConditions(Honorary honorary)
			throws PosgradoException {
    	List<Honorary> response = new ArrayList<Honorary>();
    	
    	try {
    		CriterionBuilder builder = new CriterionBuilder();
    		if (honorary.getCourse() != null) {
    			builder.add("course", honorary.getCourse());
    		}
    		if (honorary.getDebtStatus() != null) {
    			builder.add("debtStatus", honorary.getDebtStatus());
    		}
    		if (honorary.getTeacher() != null) {
    			builder.add("teacher", honorary.getTeacher());
    		}
            if (honorary.getValidityFrom() != null) {
                builder.add("validityFrom", honorary.getValidityFrom(), ExpressionOperator.GreaterEqualsThan);
            }
            
            response = this.honoraryDAO.findByCriterions(builder.toCriterions());

            // Ordena por nombre del docente
            Collections.sort(response, new Comparator<Honorary>() {
                    public int compare(Honorary a, Honorary b) {
                        return a.getTeacher().getName().compareTo(b.getTeacher().getName());
                    }
                }
            );
            if (logger.isDebugEnabled()) {
                logger.debug("Resultado ordenado por nombre del docente");
            }
    	}
        catch (DAOException e) {
            throw new PosgradoException("Excepcion buscando honorarios con criterios", e);
        }
    	
    	return response;
	}


	private void validateInput(Honorary honorary) throws MissingArgumentException, PosgradoException {
        if (honorary.getCourse() == null) {
            throw new MissingArgumentException("El curso del honorario " +
                    "no puede ser nulo");
        }
        if (honorary.getTeacher() == null) {
            throw new MissingArgumentException("El docente del honorario " +
                    "no puede ser nulo");
        }
        if (honorary.getValidityFrom() == null) {
            throw new MissingArgumentException("La fecha desde del honorario " +
                    "no puede ser nula");
        }
    }
    
    
    /**
     * @see ar.uba.fi.posgrado.economy.service.HonoraryService#activateHonoraries()
     */
    @Transactional(readOnly = false, propagation = Propagation.MANDATORY)
    public List<Honorary> activateHonoraries() throws PosgradoException {
        /* Busca los q no estan activos (Inactivos) y tienen la fecha de 
         * 'vigencia desde' menor o igual (si un dia la maquina se apaga entonces es x eso q 
         * se usa menor) a hoy.
         * Las modifica a todas en el sentido de cambiarle el estado a vigente
         */
        List<Honorary> response = new ArrayList<Honorary>();
        try {
            Date today = new Date();
            CriterionBuilder builder = new CriterionBuilder();
            builder.add("debtStatus", DebtStatus.Inactive, ExpressionOperator.Equals);
            builder.add("validityFrom", today, ExpressionOperator.LowerEqualsThan);
            logger.info("Buscando honorarios a activar con estado: " + DebtStatus.Inactive.toStringValue() +
                        ", fecha de entrada en vigencia anterior o igual a: " + CommonUtils.formatDate(today));
            
            List<Honorary> honorariesFound = this.honoraryDAO.findByCriterions(builder.toCriterions());
            logger.info("# de honoratios a activar encontradas: " + honorariesFound.size());
            
            if (logger.isDebugEnabled()) {
                logger.debug("Por activar cada uno de los honorarios encontrados ...");
            }

            // Se iteran las deudas y se las activa
            for (Honorary honorary : honorariesFound) {
                honorary.setDebtStatus(DebtStatus.Active);
                this.updateAccount(honorary);
                honorary = this.update(honorary);
                
                response.add(honorary);
            }
            logger.info("Todos los honorarios activados.");
        } catch (DAOException e) {
            throw new PosgradoException("Excepcion buscando honorarios a activar", e);
        }
        
        return response;
    }

    /**
     * Modifica la deuda de la {@link CustomerAccount  cta cte} para cuando se 
     * crean/activan {@link Honorary honorarios}s, y para cuando corresponda 
     * de acuerdo al estado.<br>
     * 
     * @param honorary Honorario
     * @throws PosgradoException
     */
    private void updateAccount(Honorary honorary) throws PosgradoException {
        if (honorary.appliesAccountModification()) {
            if (logger.isDebugEnabled()) {
                logger.debug(   "Modificando la deuda total de la cta cte a partir de creacion/" +
                                "modificacion de un honorario ...");
            }
            
            Teacher teacher = honorary.getTeacher();
            ProviderAccount account = teacher.getAccount();
            /* El valor de la deuda no se aplica directamente a la suma total de deuda
             * en la cta cte, sino q es menos descuentos y beneficios. */
            Double honoraryValue = honorary.getValue();
            account.setTotalDebt(account.getTotalDebt() + honoraryValue);
        }
    }
    
    
    /**
     * @see ar.uba.fi.posgrado.common.service.GenericService#create(java.lang.Object)
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void create(Honorary honorary) throws MissingArgumentException,
            PosgradoException {
        // Validaciones basicas
        this.validateInput(honorary);

        // El estado se define aca dependiendo de la fecha
        Date from = honorary.getValidityFrom();
        if (from.compareTo(new Date()) <= 0) {
            honorary.setDebtStatus(DebtStatus.Active);
        }
        else {
            honorary.setDebtStatus(DebtStatus.Inactive);
        }
    
        try {
            this.updateAccount(honorary);
            
            // No hay nada q evite crear una deuda... En este punto no hay restricciones
            this.honoraryDAO.save(honorary);
            logger.info("Honorario: " + honorary + " creado con exito");
        } catch (DAOException e) {
            throw new PosgradoException("Excepcion al crear el honorario: "
                    + honorary, e);
        }
    }

    /**
     * @see ar.uba.fi.posgrado.common.service.GenericService#delete(java.lang.Object)
     */
    public void delete(Honorary honorary) throws PosgradoRuntimeException,
            PosgradoException {
		try {
			this.honoraryDAO.remove(honorary);
			logger.info("Honorario: " + honorary + ", eliminado con exito");
		} catch (DAOException e) {
			throw new PosgradoException("Excepcion eliminando el honorario: "
					+ honorary, e);
		}
    }

    /**
     * @see ar.uba.fi.posgrado.common.service.GenericService#getAll()
     */
    @Transactional(readOnly = true)
    public List<Honorary> getAll() throws PosgradoRuntimeException,
            PosgradoException {
        try {
            List<Honorary> response = this.honoraryDAO.findAll();
            logger.info("Honorarios encontrados: " + response.size());
            return response;
        } catch (DAOException e) {
            throw new PosgradoException("Excepcion buscando honorarios", e);
        }
    }

    /**
     * @see ar.uba.fi.posgrado.common.service.GenericService#getById(java.lang.Long)
     */
    @Transactional(readOnly = true)
    public Honorary getById(Long id) throws PosgradoRuntimeException,
            PosgradoException {
        try {
            Honorary response = (Honorary) this.honoraryDAO.findById(id);
            logger.info("Honorario con Id: " + id + ", encontrado con exito");
            return response;
        } catch (DAOException e) {
            throw new PosgradoException(
                    "Excepcion buscando el honorario con Id: " + id, e);
        }
    }

    /**
     * @see ar.uba.fi.posgrado.common.service.GenericService#update(java.lang.Object)
     */
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public Honorary update(Honorary honorary) throws PosgradoRuntimeException,
            PosgradoException {
        // Validaciones basicas
        this.validateInput(honorary);

        // El estado se define aca dependiendo de la fecha
        Date from = honorary.getValidityFrom();
        Calendar fromCal = Calendar.getInstance();
        from.setTime(from.getTime());
        Calendar todayCal = Calendar.getInstance();
        
        if (fromCal.compareTo(todayCal) <= 0) {
        	honorary.setDebtStatus(DebtStatus.Active);
        }
        else {
        	honorary.setDebtStatus(DebtStatus.Inactive);
        }
    
        try {
            this.updateAccount(honorary);
            
            // No hay nada q evite crear una deuda... En este punto no hay restricciones
            this.honoraryDAO.update(honorary);
            logger.info("Honorario: " + honorary + " modificado con exito");
        } catch (DAOException e) {
            throw new PosgradoException("Excepcion al modificar el honorario: "
                    + honorary, e);
        }
        return honorary;
    }

	/** 
	 * @see ar.uba.fi.posgrado.common.service.GenericService#filter(java.util.List, ar.uba.fi.posgrado.common.model.User)
	 */
	public List<Honorary> filter(List<Honorary> list, User user)
			throws PosgradoRuntimeException, PosgradoException {
		if (!user.hasBodyRole()) {
			return list;
		}
		final Body body = user.getBody();
		if (logger.isDebugEnabled()) {
			logger.debug(	"El usuario tiene el rol Ente. Filtrando la informacion exclusiva para el " +
							"ente: " + body);
		}
		
		CollectionUtils.filter(list, new Predicate() {
			public boolean evaluate(Object object) {
				Honorary honorary = (Honorary)object;
				if (honorary.getCourse().getBody().equals(body)) {
					return true;
				}
				
				return false;
			}
		});
		
		return list;

	}

}
